package dd.lo.practice;

import dd.lo.util.ImageUtils;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.apache.commons.lang3.math.NumberUtils;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.ByteArrayInputStream;
import java.io.File;

public class EdgeDetectionDisplay extends Application {

    public static void start(String[] args) {
        launch(args);
    }

    private static final int DEFAULT_WIDTH = 1200;
    private static final int DEFAULT_HEIGHT = 900;

    private static enum DetectMethodEnum {
        CANNY("Canny"),
        SOBEL("Sobel算子"),
        LAPLACIAN("Laplacian算子"),
        SCHARR("Scharr滤波器"),
        ;

        private final String name;

        public String getName() {
            return name;
        }

        DetectMethodEnum(String name) {
            this.name = name;
        }
    }

    private DetectMethodEnum mode;

    private Mat srcImg;

    private SimpleStringProperty low; //第一个滞后性阈值（低）
    private SimpleStringProperty high; //第二个滞后性阈值（高）
    private SimpleStringProperty apertureSize; //Sobel算子孔径大小，必须为正奇数
    private SimpleStringProperty dx; //Sobel算子的X方向上差分阶数
    private SimpleStringProperty dy; //Sobel算子的Y方向上差分阶数
    private SimpleStringProperty scale; //Laplacian算子的可选比例因子（缩放）

    private Slider thresholdLowSlider; //Canny算法的低阈值
    private Slider thresholdHighSlider; //Canny算法的高阈值（必定大于低阈值）
    private Slider apertureSizeSlider; //Sobel算子核大小、Laplacian算子的核大小
    private Slider dxSlider; //Sobel算子的X方向的差分阶数
    private Slider dySlider; //Sobel算子的Y方向的差分阶数
    private Slider scaleSlider; //Laplacian算子、Scharr滤波器的缩放因子
    private ImageView imageView;

    private void imageProcess() {
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() {
                if (null == srcImg || srcImg.empty()) return null;
                Mat displayImg = new Mat();
                int cannyLow = NumberUtils.toInt(low.getValue(), 1);
                int cannyHigh = NumberUtils.toInt(high.getValue(), 2);
                int kernelSize = NumberUtils.toInt(apertureSize.getValue(), 1);
                int deltaX = NumberUtils.toInt(dx.getValue(), 1);
                int deltaY = NumberUtils.toInt(dy.getValue(), 1);
                double scaleVal = NumberUtils.toDouble(scale.getValue(), 1.0);
                if (mode == DetectMethodEnum.CANNY) {
                    Mat grayImg = new Mat();
                    Imgproc.cvtColor(srcImg, grayImg, Imgproc.COLOR_BGR2GRAY);
                    Imgproc.blur(grayImg, grayImg, new Size(kernelSize, kernelSize)); //使用一个内核进行降噪
                    Imgproc.Canny(grayImg, grayImg, cannyLow, cannyHigh, kernelSize); //grayImg将存储识别出来的边缘信息
                    srcImg.copyTo(displayImg, grayImg); //将srcImg拷贝到displayImg上，但以边缘信息作为掩码，这样得到的边缘信息是原图的彩色边缘
                } else if (mode == DetectMethodEnum.SOBEL) {
                    Imgproc.Sobel(srcImg, displayImg, srcImg.depth(), deltaX, deltaY, kernelSize, scaleVal);
                } else if (mode == DetectMethodEnum.LAPLACIAN) {
                    Mat grayImg = new Mat();
                    Imgproc.cvtColor(srcImg, grayImg, Imgproc.COLOR_BGR2GRAY);
                    Imgproc.blur(grayImg, grayImg, new Size(kernelSize, kernelSize)); //使用一个内核进行降噪
                    Imgproc.Laplacian(grayImg, grayImg, grayImg.depth(), kernelSize, scaleVal);
                    Core.convertScaleAbs(grayImg, displayImg); //计算绝对值，将结果转换成8位
                } else if (mode == DetectMethodEnum.SCHARR) {
                    Mat scharrX = new Mat();
                    Imgproc.Scharr(srcImg, scharrX, srcImg.depth(), 1, 0, scaleVal);
                    Mat scharrY = new Mat();
                    Imgproc.Scharr(srcImg, scharrY, srcImg.depth(), 0, 1, scaleVal);
                    Core.addWeighted(scharrX, 0.5, scharrY, 0.5, 0, displayImg);
                }
                //显示
                ImageUtils.fitImgSize(displayImg, DEFAULT_WIDTH, DEFAULT_HEIGHT);
                MatOfByte buffer = new MatOfByte();
                Imgcodecs.imencode(".jpeg", displayImg, buffer);
                Platform.runLater(() -> imageView.setImage(new Image(new ByteArrayInputStream(buffer.toArray()))));
                return null;
            }
        };
        new Thread(task).start();
    }

    @Override
    public void start(Stage stage) throws Exception {
        Button imgChooserBtn = new Button("选择图片");
        imgChooserBtn.setOnMouseClicked(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("选择图片");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home") + "/Downloads")
            );
            fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("All Images", "*.*"));
            File imgFile = fileChooser.showOpenDialog(stage);
            if (null == imgFile) return;
            srcImg = Imgcodecs.imread(imgFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);
            if (srcImg.empty()) System.out.println("图片读取失败");
            imageProcess();
        });
        Text detectMethodTip = new Text("边缘检测算法：");
        ChoiceBox<DetectMethodEnum> detectMode = new ChoiceBox<>();
        detectMode.getItems().addAll(DetectMethodEnum.CANNY, DetectMethodEnum.SOBEL, DetectMethodEnum.LAPLACIAN, DetectMethodEnum.SCHARR);
        detectMode.setConverter(new StringConverter<DetectMethodEnum>() {
            @Override
            public String toString(DetectMethodEnum object) {
                return object.getName();
            }
            @Override
            public DetectMethodEnum fromString(String string) {
                return null;
            }
        });
        detectMode.valueProperty().addListener((observable, oldValue, newValue) -> {
            mode = newValue;
            imageProcess();
        });
        detectMode.setValue(DetectMethodEnum.CANNY); //初始化值
        //CANNY的高低阈值设置
        Text thresholdLowTip = new Text("CANNY低阈值：");
        low = new SimpleStringProperty();
        Text thresholdLowText = new Text();
        thresholdLowText.textProperty().bind(low);
        thresholdLowSlider = new Slider(0, 254, 80);
        thresholdLowSlider.setPrefWidth(DEFAULT_WIDTH * 0.4);
        thresholdLowSlider.setShowTickLabels(true);
        thresholdLowSlider.setShowTickMarks(true);
        thresholdLowSlider.setSnapToTicks(true);
        thresholdLowSlider.setMajorTickUnit(1.0);
        thresholdLowSlider.setMinorTickCount(4);
        thresholdLowSlider.setBlockIncrement(1.0);
        thresholdLowSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            low.set(Math.round(newValue.doubleValue()) + "");
            double highVal = thresholdHighSlider.getValue();
            if (newValue.doubleValue() > highVal) { //低的比高的大，则把高的拔起来
                Platform.runLater(() -> thresholdHighSlider.setValue(newValue.doubleValue() + 1));
            }
            imageProcess();
        });
        low.set(Math.round(thresholdLowSlider.getValue()) + ""); //初始化值
        Text thresholdHighTip = new Text("CANNY高阈值：");
        high = new SimpleStringProperty();
        Text thresholdHighText = new Text();
        thresholdHighText.textProperty().bind(high);
        thresholdHighSlider = new Slider(0, 254, 240);
        thresholdHighSlider.setPrefWidth(DEFAULT_WIDTH * 0.4);
        thresholdHighSlider.setShowTickLabels(true);
        thresholdHighSlider.setShowTickMarks(true);
        thresholdHighSlider.setSnapToTicks(true);
        thresholdHighSlider.setMajorTickUnit(1.0);
        thresholdHighSlider.setMinorTickCount(4);
        thresholdHighSlider.setBlockIncrement(1.0);
        thresholdHighSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue.doubleValue() < 1.0) {
                Platform.runLater(() -> thresholdHighSlider.setValue(1.0));
                return;
            }
            high.set(Math.round(newValue.doubleValue()) + "");
            double lowVal = thresholdLowSlider.getValue();
            if (newValue.doubleValue() <= lowVal) { //低的比高的大，则把低的压下去
                Platform.runLater(() -> thresholdLowSlider.setValue(newValue.doubleValue() - 1));
            }
            imageProcess();
        });
        high.set(Math.round(thresholdHighSlider.getValue()) + ""); //初始化值
        //核心大小的设定
        Text apertureSizeTip = new Text("算子孔径大小：");
        apertureSize = new SimpleStringProperty();
        Text apertureSizeText = new Text();
        apertureSizeText.textProperty().bind(apertureSize);
        apertureSizeSlider = new Slider(3, 7, 3);
        apertureSizeSlider.setPrefWidth(DEFAULT_WIDTH * 0.25);
        apertureSizeSlider.setShowTickLabels(true);
        apertureSizeSlider.setShowTickMarks(true);
        apertureSizeSlider.setSnapToTicks(true);
        apertureSizeSlider.setMajorTickUnit(2.0);
        apertureSizeSlider.setMinorTickCount(0);
        apertureSizeSlider.setBlockIncrement(2.0);
        apertureSizeSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            apertureSize.set(Math.round(newValue.doubleValue()) + "");
            imageProcess();
        });
        apertureSize.set(Math.round(apertureSizeSlider.getValue()) + ""); //初始化值
        //dx、dy的设定
        Text dxTip = new Text("Sobel算子X方向差分阶数：");
        dx = new SimpleStringProperty();
        Text dxText = new Text();
        dxText.textProperty().bind(dx);
        dxSlider = new Slider(0, 3, 1);
        dxSlider.setPrefWidth(DEFAULT_WIDTH * 0.25);
        dxSlider.setShowTickLabels(true);
        dxSlider.setShowTickMarks(true);
        dxSlider.setSnapToTicks(true);
        dxSlider.setMajorTickUnit(1.0);
        dxSlider.setMinorTickCount(0);
        dxSlider.setBlockIncrement(1.0);
        dxSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            dx.set(Math.round(newValue.doubleValue()) + "");
            imageProcess();
        });
        dx.set(Math.round(dxSlider.getValue()) + ""); //初始化值
        Text dyTip = new Text("Sobel算子Y方向差分阶数：");
        dy = new SimpleStringProperty();
        Text dyText = new Text();
        dyText.textProperty().bind(dy);
        dySlider = new Slider(0, 3, 1);
        dySlider.setPrefWidth(DEFAULT_WIDTH * 0.25);
        dySlider.setShowTickLabels(true);
        dySlider.setShowTickMarks(true);
        dySlider.setSnapToTicks(true);
        dySlider.setMajorTickUnit(1.0);
        dySlider.setMinorTickCount(0);
        dySlider.setBlockIncrement(1.0);
        dySlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            dx.set(Math.round(newValue.doubleValue()) + "");
            imageProcess();
        });
        dy.set(Math.round(dySlider.getValue()) + ""); //初始化值
        //scale参数设定
        Text scaleTip = new Text("Scale参数：");
        scale = new SimpleStringProperty();
        Text scaleText = new Text();
        scaleText.textProperty().bind(scale);
        scaleSlider = new Slider(0, 10, 1);
        scaleSlider.setPrefWidth(DEFAULT_WIDTH * 0.25);
        scaleSlider.setShowTickLabels(true);
        scaleSlider.setShowTickMarks(false);
        scaleSlider.setSnapToTicks(false);
        scaleSlider.setBlockIncrement(0.1);
        scaleSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            scale.set(newValue.toString());
            imageProcess();
        });
        scale.set(Math.round(scaleSlider.getValue()) + ""); //初始化值
        //关闭按钮
        Button exitBtn = new Button("关闭");
        exitBtn.setOnMouseClicked(e -> Platform.exit());
        VBox rows = new VBox(0);
        rows.setPadding(new Insets(10, 10, 0, 10));
        HBox row0 = new HBox(10);
        row0.setPadding(new Insets(5, 0, 5, 0));
        row0.getChildren().addAll(imgChooserBtn, detectMethodTip, detectMode, exitBtn);
        HBox row1 = new HBox(10);
        row1.setPadding(new Insets(5, 0, 5, 0));
        row1.getChildren().addAll(thresholdLowTip, thresholdLowSlider, thresholdLowText, thresholdHighTip, thresholdHighSlider, thresholdHighText);
        HBox row2 = new HBox(10);
        row2.setPadding(new Insets(5, 0, 5, 0));
        row2.getChildren().addAll(apertureSizeTip, apertureSizeSlider, apertureSizeText, scaleTip, scaleSlider, scaleText);
        HBox row3 = new HBox(10);
        row3.setPadding(new Insets(5, 0, 5, 0));
        row3.getChildren().addAll(dxTip, dxSlider, dxText, dyTip, dySlider, dyText);
        rows.getChildren().addAll(row0, row1, row2, row3);
        BorderPane root = new BorderPane();
        imageView = new ImageView(); //imageView放到一个pane的中间
        root.setCenter(imageView);
        root.setBottom(rows);
        Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT + 550);
        stage.setScene(scene);
        stage.setTitle("边缘检测演示（Canny、Sobel算子、Laplacian算子、Scharr滤波器）");
        stage.setResizable(false);
        stage.show();
    }
}
